package edu.thu.thss.rsa.yxy;

import java.math.BigInteger;

/**
 * Fast Implementation of RSA
 * 
 * @author Andy Gao
 *
 */
public class PrivateKeyFast extends PublicKeyFast{
    @SuppressWarnings("unused")
	private BigInteger d; // decryption exponent
	private BigInteger p; // first prime
	private BigInteger q; // second prime
	private BigInteger dp, dq, c2; // for fast decryption
//	private BigInteger[] dp_additionChains, dq_additionChains;

	public PrivateKeyFast(BigInteger d, BigInteger p, BigInteger q, BigInteger n) {
		super(n);
		
		this.d = d;
		this.p = p;
		this.q = q;
		
		BigInteger pM1 = p.subtract(BigInteger.ONE);

		BigInteger qM1 = q.subtract(BigInteger.ONE);

		// remaining stuff needed for fast CRT decryption
		dp = d.remainder(pM1);
		dq = d.remainder(qM1);
		
//		ShortAdditionChains sac = new ShortAdditionChains(dp);
//		dp_additionChains = sac.getAdditonChains();
//		sac = new ShortAdditionChains(dq);
//		dq_additionChains = sac.getAdditonChains();
		
		c2 = p.modInverse(q);
	}
	
	// RSADecrypt: decryption function, fast CRT version
	public BigInteger RSADecrypt(BigInteger c) {
		BigInteger cMp = c.mod(p);
		BigInteger cMq = c.mod(q);

//		BigInteger cDp = fastModPow(cMp, dp_additionChains, p);
//		BigInteger cDq = fastModPow(cMq, dq_additionChains, q);
		BigInteger cDp = fastModPow(cMp, dp, p);
		BigInteger cDq = fastModPow(cMq, dq, q);
		BigInteger u = ((cDq.subtract(cDp)).multiply(c2)).remainder(q);
		if (u.compareTo(BigInteger.ZERO) < 0)
			u = u.add(q);
		return cDp.add(u.multiply(p));
	}
	
	// RSASign: same as decryption for RSA (since it is a symmetric PKC)
	public BigInteger RSASign(BigInteger m) {
		// return m.modPow(d, n);
		return RSADecrypt(m); // use fast CRT version
	}

	public BigInteger RSASignAndEncrypt(BigInteger m, PublicKey other) {
		// two ways to go, depending on sizes of n and other.getN()
		if (n.compareTo(other.getN()) > 0)
			return RSASign(other.RSAEncrypt(m));
		else
			return other.RSAEncrypt(RSASign(m));
	}

	public BigInteger RSADecryptAndVerify(BigInteger c, PrivateKey other) {
		// two ways to go, depending on sizes of n and other.getN()
		if (n.compareTo(other.getN()) > 0)
			return other.RSAVerify(RSADecrypt(c));
		else
			return RSADecrypt(other.RSAVerify(c));
	}

}
